﻿//
// © Algobox Ltd - Benn Eichhorn
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using Algobox.Structure.Finance.Strategies.MarketFiller;
using Algobox.Objects.Finance.Assets.Stock;
using System.Threading;

namespace Algobox.Structure.Finance.Strategies.Terrapin
{
    public class TerrapinUnit : INotifyPropertyChanged, IMarketFillerCallback, Algobox.Structure.Finance.Strategies.IStrategyUnit
    {
        #region Constructors

        public TerrapinUnit(
            ITerrapinUnitCallback caller,
            Algobox.Feeds.Finance.Trading.ITradingFeedAdaptor tradingFeed,
            TerrapinStock strategy,
            bool isBuy,
            double entryPrice,
            double exitPrice,
            uint quantity)
        {
            _caller = caller;
            _tradingFeed = tradingFeed;
            _strategy = strategy;
            _isBuy = isBuy;
            _priceEntryTarget = entryPrice;
            _priceExitTarget = exitPrice;
            _quantityInitial = quantity;
            _quantityRemain = quantity;
            _logId = _strategy.Name;
            _lock = new object();

            if (_strategy.Security.Currency == Objects.Finance.Currencies.Currency.GBX)
                _divisor = 100;
            else
                _divisor = 1;

            if(Log.IsInfoEnabled)
                Log.Info("{0}|Creating TerrapinUnit of {1}@{2} with an initial target at {3}", _logId, quantity, entryPrice, exitPrice);

            Profit = CalculateProfit(exitPrice);
        }

        #endregion
        //////////
        #region Member Variables

        /// <summary>
        /// Logger used by this class
        /// </summary>        
        private static readonly NLog.Logger Log = NLog.LogManager.GetCurrentClassLogger();

        public event PropertyChangedEventHandler PropertyChanged;

        ITerrapinUnitCallback _caller;
        private Algobox.Feeds.Finance.Trading.ITradingFeedAdaptor _tradingFeed;

        private object _lock;

        private string _logId;
        private TerrapinStock _strategy;
        private uint _quantityInitial;
        private uint _quantityRemain;
        private readonly bool _isBuy;
        private int _divisor;

        private double _priceEntryTarget;
        private double _priceEntryActual;
        private double _priceExitTarget;
        private double _priceExitActual;
        private double _profit;

        private MarketFillOrder _orderEntry;
        private MarketFillOrder _orderExit;
        
        DateTime _timeStampEntry;
        DateTime _timeStampExit;

        #endregion
        //////////
        #region Properties

        public string Id { get { return this.GetType().Name + "|" + _logId; } }

        public Strategy Strategy { get { return _strategy; } }
        public string Name { get { return _strategy.Name; } }
        public Side Direction { get { return _isBuy ? Side.Long : Side.Short; } }
        public uint Quantity { get { return _quantityInitial; } private set { _quantityInitial = value; NotifyPropertyChanged("Quantity"); } }
        public uint Remaining { get { return _quantityRemain; } private set { _quantityRemain = value; NotifyPropertyChanged("Remaining"); } }
        public double EntryTarget { get { return _priceEntryTarget; } }
        public double EntryActual { get { return _priceEntryActual == 0 ? _priceEntryTarget : _priceEntryActual; } private set { _priceEntryActual = value; NotifyPropertyChanged("EntryActual"); NotifyPropertyChanged("SlipEntry"); } }
        public bool IsBuy { get { return _isBuy; } }

        public double ExitTarget 
        { 
            get { return _priceExitTarget; }
            set
            {
                if (Log.IsInfoEnabled)
                    Log.Info("{0}|ExitTarget is changing from {1} to {2}", _logId, _priceExitTarget, value); 
                _priceExitTarget = value; 
                NotifyPropertyChanged("ExitTarget");
            } 
        }

        public double ExitActual { get { return _priceExitActual; } private set { _priceExitActual = value; NotifyPropertyChanged("ExitActual"); NotifyPropertyChanged("SlipExit"); } }


        public double Profit { get { return _profit; } private set { _profit = value; NotifyPropertyChanged("Profit"); } }

        public double SlipEntry { get { return _priceEntryActual == 0 ? 0 : _isBuy ? _priceEntryTarget - _priceEntryActual : _priceEntryActual - _priceEntryTarget; } }
        public double SlipExit { get { return _priceExitActual == 0 ? 0 : _isBuy ? _priceExitActual - _priceExitTarget : _priceExitTarget - _priceExitActual; } }
        public string TimeStampEntry { get { return _timeStampEntry.ToLongTimeString(); } }
        public string TimeStampExit { get { return _timeStampExit.ToLongTimeString(); } }

        #endregion
        //////////
        #region Functions

        public void EnterUnit(DateTime timeStamp)
        {
            if (_orderEntry == null)
            {
                lock (_lock)
                {
                    if (_orderEntry == null)
                    {
                        if (Log.IsTraceEnabled)
                            Log.Trace("{0}|Entering unit at {1}", _logId, timeStamp.TimeOfDay);

                        _orderEntry = new MarketFillOrder(_tradingFeed, this, _strategy.Security, _strategy.Account, _isBuy, _priceEntryTarget, _quantityInitial, Objects.Finance.Orders.Order.TimeInForce.DAY);
                        _orderEntry.Start();
                        _timeStampEntry = timeStamp;
                        NotifyPropertyChanged("TimeStampEntry");
                    }
                    else
                    {
                        Log.Error("{0}|EnterUnit Attempting to enter unit more than once!", _logId, timeStamp.TimeOfDay);

                        throw new InvalidOperationException("Should not enter more than once!");
                    }
                }
            }
            else
            {
                Log.Error("{0}|EnterUnit Attempting to enter unit more than once!", _logId);
#if DEBUG
                throw new InvalidOperationException("Should not enter more than once!");
#endif
            }
        }

        public void ExitUnit(DateTime timeStamp)
        {
            if (_orderExit == null)
            {
                if (_quantityRemain != 0)
                {
                    lock (_lock)
                    {
                        if (_orderExit == null && _quantityRemain != 0)
                        {
                            if (Log.IsTraceEnabled)
                                Log.Trace("{0}|Exiting unit at {1}", _logId, timeStamp.TimeOfDay);

                            _orderExit = new MarketFillOrder(_tradingFeed, this, _strategy.Security, _strategy.Account, !_isBuy, _priceExitTarget, _quantityInitial, Objects.Finance.Orders.Order.TimeInForce.DAY);
                            _orderExit.Start();
                            _timeStampExit = timeStamp;
                            NotifyPropertyChanged("TimeStampExit");
                        }
                    }
                }
                else
                {
                    Log.Error("{0}|ExitUnit Can't exit with 0 remaining quantity!", _logId);
#if DEBUG
                    throw new InvalidOperationException("Can't exit with 0 remaining quantity!");
#endif
                }
            }
            else
            {
                Log.Error("{0}|ExitUnit Attempting to exit unit more than once!", _logId);
#if DEBUG
                throw new InvalidOperationException("Should not exit more than once!");
#endif
            }
        }

        public void OnMarketFillerCompleted(MarketFillOrder caller)
        {
            if (caller == _orderEntry || caller == _orderExit)
            {
                lock (_lock)
                {
                    Profit = CalculateProfit(caller.AveragePrice);

                    if (caller == _orderEntry)
                    {
                        if (Log.IsInfoEnabled)
                            Log.Info("{0}|Entry order filled, achieved {1}@{2}", _logId, caller.Quantity, caller.AveragePrice);

                        EntryActual = caller.AveragePrice;
                        _orderEntry = null;
                    }
                    else if (caller == _orderExit)
                    {
                        if (Log.IsInfoEnabled)
                            Log.Info("{0}|Unit complete, exit order filled, achieved {1}@{2}", _logId, caller.Quantity, caller.AveragePrice);

                        ExitActual = caller.AveragePrice;
                        Remaining = 0;
                        _caller.OnTerrapinUnitComplete(this);
                        _orderExit = null;
                    }
                }
            }
            else
            {
                Log.Error("{0}|OnMarketFillerCompleted Unhandled MarketFillOrder({1})", _logId, caller.ToString());
            }
        }

        public void OnMarketFillerFailed(MarketFillOrder caller)
        {
            if (caller == _orderEntry || caller == _orderExit)
            {
                lock (_lock)
                {
                    if (caller == _orderEntry)
                    {
                        if (caller.QuantityFilled > 0)
                        {
                            if (Log.IsWarnEnabled)
                                Log.Warn("{0}|Terrapin entry leg partially failed but will continue with remaining quantity achieved {1}@{2}", _logId, caller.Quantity, caller.AveragePrice);

                            EntryActual = caller.AveragePrice;
                            Quantity = caller.Quantity;
                            _quantityRemain = caller.Quantity;
                            _orderEntry = null;
                        }
                        else
                        {
                            if (Log.IsErrorEnabled)
                                Log.Error("{0}|Terrapin entry leg failed, disabling this Terrapin strategy", _logId, caller.Quantity, caller.AveragePrice);

                            _caller.OnTerrapinUnitFailed(this);
                        }
                    }
                    else if (caller == _orderExit)
                    {
                        if (caller.QuantityFilled > 0)
                        {
                            ExitActual = caller.AveragePrice;
                            Remaining -= caller.Quantity;

                            if (Log.IsWarnEnabled)
                                Log.Warn("{0}|Terrapin exit leg partially failed we recieved only {1}@{2} leaving a difference of {3}", _logId, _quantityRemain, caller.AveragePrice);

                            _caller.OnTerrapinUnitFailed(this);
                        }
                        else
                        {
                            if (Log.IsErrorEnabled)
                                Log.Error("{0}|Terrapin exit leg failed, disabling this Terrapin strategy leaving open position of {1}", _logId, _quantityRemain);

                            _caller.OnTerrapinUnitFailed(this);
                        }
                    }
                }
            }
            else
            {
                Log.Error("{0}|OnMarketFillerCompleted Unhandled MarketFillOrder({1})", _logId, caller.ToString());
            }
        }

        public void OnBestPrice(double price, DateTime timeStamp)
        {
            if (Monitor.TryEnter(_lock, 10))
            {
                try
                {
                    if (_orderEntry == null && _orderExit == null)
                    {
                        if (_isBuy && price < _priceExitTarget)
                        {
                            if (Log.IsInfoEnabled)
                                Log.Info("{0}|Exiting unit as latest price {1} is < exit price {2}", _logId, price, _priceExitTarget);

                            ExitUnit(timeStamp);
                        }
                        else if (!_isBuy && price > _priceExitTarget)
                        {
                            if (Log.IsInfoEnabled)
                                Log.Info("{0}|Exiting unit as latest price {1} is > exit price {2}", _logId, price, _priceExitTarget);

                            ExitUnit(timeStamp);
                        }
                    }

                    Profit = CalculateProfit(price);
                }
                finally
                {
                    Monitor.Exit(_lock);
                }
            }
        }

        private double CalculateProfit(double exitPrice)
        {
            double entryPrice;
            if (_priceEntryActual == 0)
                entryPrice = _priceEntryTarget;
            else
                entryPrice = _priceEntryActual;

            return _isBuy ?
                (exitPrice - entryPrice) * _quantityInitial / _divisor:
                (entryPrice - exitPrice) * _quantityInitial / _divisor;
        }

        private void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
        
        #endregion
        //////////
        #region Enums


        #endregion


    }
}